package com.sian.security.utils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import org.apache.log4j.Logger;

import java.security.SecureRandom;
import java.security.spec.KeySpec;


public class DES3
{
	private static Logger logger = org.apache.log4j.LogManager.getLogger(DES3.class);


	public static final String KEY_SPEC_PBE_DES="PBEWithMD5AndDES";
	
	// DES: Data Encryption Standard
	public static final String KEY_SPEC_DES="DES";	//="DES/ECB/PKCS5Padding";
	// DES with CBC
	public static final String KEY_SPEC_DES_CBC_PKCS5PADDING="DES/CBC/PKCS5Padding";

	// DESede:
	public static final String KEY_SPEC_DESede="DESede";
	//private String KEY_SPEC_DES_CBC_PKCS5PADDING="DES/CBC/PKCS5Padding";

	private SecretKey secKey = null;
	private String passPhrase = null;
	private Long salt = null;
	private String type = "DES";
	
	final int iterationCount = 19;

	public DES3(String passPhrase, Long salt, String type) throws Exception
	{
		this.passPhrase = passPhrase;
		this.salt = salt;
		if(null != type && 0 != type.trim().length())
		{
			this.type = type;
		}

		if(null == passPhrase)
		{
			KeyGenerator keygen = KeyGenerator.getInstance("DES");
			secKey = keygen.generateKey();
		}
	}

	public String encrypt(String str) throws Exception
	{
		Cipher ecipher = null;

		try
		{
			if(null != salt)
			{
				IvParameterSpec iv = new IvParameterSpec(Long.toHexString(salt.longValue()).getBytes());

				if(type.toUpperCase().equals(KEY_SPEC_PBE_DES.toUpperCase()))
				{
					KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), iv.getIV(), iterationCount);
					SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_SPEC_PBE_DES);
					secKey = keyFactory.generateSecret(keySpec);

					ecipher = Cipher.getInstance(secKey.getAlgorithm());
					ecipher.init(Cipher.ENCRYPT_MODE, secKey, new PBEParameterSpec(iv.getIV(), iterationCount));
				}
				else if(type.toUpperCase().equals(KEY_SPEC_DES_CBC_PKCS5PADDING.toUpperCase()))
				{
					KeySpec keySpec = new DESKeySpec(passPhrase.getBytes());
					SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
					secKey = keyFactory.generateSecret(keySpec);

					ecipher = Cipher.getInstance(KEY_SPEC_DES_CBC_PKCS5PADDING);
					ecipher.init(Cipher.ENCRYPT_MODE, secKey, iv, new SecureRandom());
				}
			}
			else
			{
				if(type.toUpperCase().equals(KEY_SPEC_DESede.toUpperCase()))
				{
					KeySpec keySpec = new DESedeKeySpec(Padding(passPhrase).getBytes());
					SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_SPEC_DESede);
					secKey = keyFactory.generateSecret(keySpec);

					ecipher = Cipher.getInstance(secKey.getAlgorithm());
					ecipher.init(Cipher.ENCRYPT_MODE, secKey);
				}
				else if(type.toUpperCase().equals(KEY_SPEC_DES.toUpperCase()))
				{
					KeySpec keySpec = new DESKeySpec(passPhrase.getBytes());
					SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_SPEC_DES);
					secKey = keyFactory.generateSecret(keySpec);
	
					ecipher = Cipher.getInstance(secKey.getAlgorithm());
					ecipher.init(Cipher.ENCRYPT_MODE, secKey);
				}
			}

			// Encode the string into bytes using utf-8
			byte[] buffer = str.getBytes("UTF8");
			// Encrypt
			byte[] enc = ecipher.doFinal(buffer);
			// Encode bytes to base64 to get a string
			return new sun.misc.BASE64Encoder().encode(enc);
		}
		catch (Exception e) 
		{
			logger.error(e.getMessage(), e);
			throw e;
		}
	}

	public String decrypt(String str) throws Exception 
	{
		Cipher dcipher = null;

		try
		{
			if(null != salt)
			{
				IvParameterSpec iv = new IvParameterSpec(Long.toHexString(salt.longValue()).getBytes());

				if(type.toUpperCase().equals(KEY_SPEC_PBE_DES.toUpperCase()))
				{
					KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), iv.getIV(), iterationCount);
					SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_SPEC_PBE_DES);
					secKey = keyFactory.generateSecret(keySpec);
					
					dcipher = Cipher.getInstance(secKey.getAlgorithm());
					dcipher.init(Cipher.DECRYPT_MODE, secKey, new PBEParameterSpec(iv.getIV(), iterationCount));
				}
				else if(type.toUpperCase().equals(KEY_SPEC_DES_CBC_PKCS5PADDING.toUpperCase()))
				{
					KeySpec keySpec = new DESKeySpec(passPhrase.getBytes());
					SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
					secKey = keyFactory.generateSecret(keySpec);

					dcipher = Cipher.getInstance(KEY_SPEC_DES_CBC_PKCS5PADDING);
					dcipher.init(Cipher.DECRYPT_MODE, secKey, iv, new SecureRandom());
				}
			}
			else
			{
				if(type.toUpperCase().equals(KEY_SPEC_DESede.toUpperCase()))
				{
					KeySpec keySpec = new DESedeKeySpec(Padding(passPhrase).getBytes());
					SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_SPEC_DESede);
					secKey = keyFactory.generateSecret(keySpec);

					dcipher = Cipher.getInstance(secKey.getAlgorithm());
					dcipher.init(Cipher.DECRYPT_MODE, secKey);
				}
				else if(type.toUpperCase().equals(KEY_SPEC_DES.toUpperCase()))
				{
					KeySpec keySpec = new DESKeySpec(passPhrase.getBytes());
					SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_SPEC_DES);
					secKey = keyFactory.generateSecret(keySpec);
	
					dcipher = Cipher.getInstance(secKey.getAlgorithm());
					dcipher.init(Cipher.DECRYPT_MODE, secKey);
				}
			}
			
			// Decode base64 to get bytes
			byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(str);
			// Decrypt
			byte[] buffer = dcipher.doFinal(dec);
			// Decode using utf-8
			return new String(buffer, "UTF8");
		}
		catch (Exception e) 
		{
			logger.error(e.getMessage(), e);
			throw e;
		}
	}

	public String Padding(String input)
	{
		int len = input.length();
		if(len > 24)
		{
			throw new RuntimeException("Input is too long.");
		}
		
		StringBuffer outputBuffer = new StringBuffer(input);
		outputBuffer.setLength(24);
		
		return outputBuffer.toString();
	}
}
